--[[
	Shows sell prizes in the tooltip and updates them while visiting merchants.
	
	Copyright (C) Udorn (Blackhand)
	
	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.	
--]]

vendor.SellPrizes = vendor.Vendor:NewModule("SellPrizes", "AceEvent-3.0", "AceHook-3.0")
local L = vendor.Locale.GetInstance()
local self = vendor.SellPrizes
local iprice = LibStub("ItemPrice-1.1")
local log = vendor.Debug:new("SellPrizes")

local SELL_PRIZES_VERSION = 3

StaticPopupDialogs["SELL_PRIZES_DB_RESET"] = {
	text = L["Do you really want to reset the database?"];
	button1 = L["Yes"],
	button2 = L["No"],
	OnAccept = function()
		self.db.global.prizes = {}
		vendor.Vendor:Print(L["Database of sell prizes for current account where reset."]) 	
	end,
 	timeout = 0,
 	whileDead = 1,
 	hideOnEscape = 1
}

--[[
	Migrates the database.
--]]
local function _MigrateDb(self)
	local oldDb = vendor.Vendor.oldDb:AcquireDBNamespace("Scanner")
	if (not self.db.global.version or self.db.global.version < 3) then
		-- migrate old "account" to "global"
		self.db.global.prizes = oldDb.account.prizes or {}
		oldDb.account.prizes = nil
	end	
	self.db.global.version = SELL_PRIZES_VERSION
end

-- recognition of "chargeable" items, found in ItemCacheData
local function _SplitPluralPattern(pattern)
	local head, singular, plural, tail = strmatch(pattern, "^(.*)|4(.+):(.+);(.*)$")
	head = head or ""
	tail = tail or ""
	if (singular) then
		singular = head..singular..tail
	end
	if (plural) then
		plural = head..plural..tail
	end	
	return singular, plural
end
local ITEM_SPELL_CHARGES_PATTERN_1, ITEM_SPELL_CHARGES_PATTERN_2 = _SplitPluralPattern(gsub(ITEM_SPELL_CHARGES, "%%.-d", "(%%d+)"))

--[[
	Sets the given value in the profile.
--]]
local function _SetValue(info, value)
	self.db.profile[info.arg] = value;
end

--[[
	Returns the given value from the profile.
--]]
local function _GetValue(info)
	return self.db.profile[info.arg]
end

--[[
	Extracts the item code from the given link.
--]]
function _ExtractItemId(link)
   	local _, _, rtn = strfind(link, "(%d+):")
	return tonumber(rtn);
end

--[[
	Returns the item price found in the library. The return value nil means that nothing
	is known about the item. The return value 0 means that the item is not sellable.
--]]
--local function _GetSellPrice(id)
--	-- algorithm taken from ItemPrice-1.1
--	if (id and id <= vendor.NumSellPrices and id > 0) then
--		local index = id * 4
--		local a, b, c, d = string.byte(vendor.SellPricesList, index - 3, index)
--		if (a == 126 and b == 126 and c == 126 and d == 126) then 
--			return 0
--		elseif (a == 0 and b == 0 and c == 0 and d == 0) then
--			return nil
--		else
--			return a * 16777216 + b * 65536 + c * 256 + d
--		end
--	end
--	return nil
--end
	
--[[
	Options for this module.
--]]
local function _CreateOptions()
	vendor.Vendor.options2.args.sellPrizes = {
		type = "group",
		name = L["SellPrizes"],
		desc = L["SellPrizes"],
		args = {
			show = {
				type = "toggle",
				name = L["Show sell prizes"],
				desc = L["Selects whether the sell prizes should be shown in the GameTooltip."],
				get = _GetValue,
				set = _SetValue,
				arg = "showPrizes",
				order = 1,
			},
			showLabel = {
				type = "toggle",
				name = L["Show label"],
				desc = L["Selects whether the sell prize label should be shown in the GameTooltip."],
				get = _GetValue,
				set = _SetValue,
				arg = "showLabel",
				order = 2,
			},
			reset = {
				type = "execute",
				name = L["Reset database"],
				desc = L["Resets the database of sell prizes for the current account. Will not delete the predelivered default database."],
				func = function() StaticPopup_Show("SELL_PRIZES_DB_RESET") end,
				order = 3,
			},
		}
	}
	vendor.Vendor.AceConfigRegistry:RegisterOptionsTable("AuctionMaster SellPrizes", vendor.Vendor.options2.args.sellPrizes)
	vendor.Vendor.AceConfigDialog:AddToBlizOptions("AuctionMaster SellPrizes", L["SellPrizes"], "AuctionMaster")
end

--[[
	Builds a crc checksum.
--]]
local bit_band, bit_bxor, bit_rshift, str_byte, str_len = bit.band, bit.bxor, bit.rshift, string.byte, string.len
local consts = { 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D }
local function _GetChecksum(s)
	local crc, l, i = 0xFFFFFFFF, str_len(s)
	for i = 1, l, 1 do
		crc = bit_bxor(bit_rshift(crc, 8), consts[bit_band(bit_bxor(crc, str_byte(s, i)), 0xFF) + 1])
	end
	return bit_bxor(crc, -1)
end

--[[
	Remembers checksum, a tiny little bit obfuscated.
--]]
local function _BuildChecksum(self)
	-- we need a deterministic sequence
	local seq = {}
	for k,v in pairs(self.db.global.prizes) do
		table.insert(seq, k)
		table.insert(seq, v)
	end	
	-- build checksum from sorted items
	table.sort(seq)
	local msg = ""
	for k,v in pairs(seq) do
		msg = msg..v..","
	end
	vendor.Vendor.db.global.checksum = _GetChecksum(msg)
end

--[[
	Checks whether a message should be printed for new sell prices.
--]]
local function _CheckForUpdatedPrices(self)
	log:Debug("_CheckForUpdatedPrices enter")
	local num = 0
	for k,v in pairs(self.db.global.prizes) do
		num = num + 1
	end	
	if (num > 0) then
		-- delay the message
		msgTask = vendor.SellPriceMsgTask:new(num);
		vendor.TaskQueue:AddTask(msgTask);	
	end
	log:Debug("_CheckForUpdatedPrices exit")
end

--[[
	Checks whether some sell prices are contained in the database.
--]]
local function _CleanUpSellPrices(self)
	log:Debug("_CleanUpSellPrices enter")
	local changed = false
	for k,v in pairs(self.db.global.prizes) do
		local sellPrice = iprice:GetPrice(k)
		local itemLinkKey = k..":0:0:0:0:0"
		local itemLink = vendor.Items:GetItemLink(itemLinkKey)
		local name = GetItemInfo(itemLink)
		--log:Debug("check id: "..k.." sellPrice: "..(sellPrice or "NA").." self: "..v.." name: "..(name or "NA"))
		if (v == sellPrice) then
			--log:Debug("_CleanUpSellPrices remove: "..k)
			self.db.global.prizes[k] = nil
			changed = true
		end
	end
	if (changed) then
		_BuildChecksum(self)
	end
	log:Debug("_CleanUpSellPrices exit")
end

--[[
	Parse the content of the tooltip.
--]]
local function _ScanTooltip(tooltip)
	tooltip.isUnsellable = false
	tooltip.isChargeable = false
	for i=1, tooltip:NumLines(), 1 do
		local txt = getglobal("AuctionMasterSellPriceTooltipTextLeft"..i):GetText()
		if (txt) then
			if (txt == ITEM_UNSELLABLE) then
				tooltip.isUnsellable = true
			elseif (strfind(txt, ITEM_SPELL_CHARGES_NONE)) then
				tooltip.isChargeable = true			
			elseif (ITEM_SPELL_CHARGES_PATTERN_2 and ITEM_SPELL_CHARGES_PATTERN_1 and (strmatch(txt, ITEM_SPELL_CHARGES_PATTERN_2) or strmatch(txt, ITEM_SPELL_CHARGES_PATTERN_1))) then
				tooltip.isChargeable = true
			end
		end
	end	
end

--[[
	Checks whether the database version has increased.
--]]
local function _UpdateVersion(self)
	if (not self.db.global.version or self.db.global.version <= 1) then
		log:Debug("_UpdateVersion remove old sell prices")
		-- they may be bogus
		self.db.global.prizes = {}
	end
	self.db.global.version = SELL_PRIZES_VERSION
end

--[[
	Initializes the addon.
--]]
function vendor.SellPrizes:OnInitialize()
	self.db = vendor.Vendor.db:RegisterNamespace("SellPrizes", {
		global = {
			prizes = {},
		},
		profile = {
			showPrizes = true,
			showLabel = true,
		}
	});
	_MigrateDb(self)
	_UpdateVersion(self)
	_CreateOptions();
end

--[[
	Initializes the module.
--]] 
function vendor.SellPrizes:OnEnable()
	log:Debug("SellPrizes:OnEnable enter")
   	self:RegisterEvent("MERCHANT_SHOW");
	-- create our own GameTooltip
	self.tooltip = CreateFrame("GameTooltip", "AuctionMasterSellPriceTooltip", UIParent, "GameTooltipTemplate")
	-- the following two are needed to be able to scan the text of the tooltip
	self.tooltip:SetFrameStrata("TOOLTIP")
	self.tooltip:SetOwner(UIParent, "ANCHOR_NONE")
	self.tooltip:SetScript("OnTooltipAddMoney", function(tooltip, sellPrice) tooltip.sellPrice = sellPrice end)
	vendor.TooltipHook:AddAppender(self, 10);
	vendor.Vendor.db.global.hasChargesPattern = (ITEM_SPELL_CHARGES_PATTERN_1 ~= nil) and (ITEM_SPELL_CHARGES_PATTERN_2 ~= nil)
	_CleanUpSellPrices(self)
	-- currently deactivated because of too much effort to work on the many mails
--	if (vendor.Vendor.db.global.hasChargesPattern) then
--		_CheckForUpdatedPrices(self);
--	end
	log:Debug("SellPrizes:OnEnable exit")
end

--[[
	Merchant frame has been opened.
--]]
function vendor.SellPrizes:MERCHANT_SHOW() 
	log:Debug("SellPrizes:MERCHANT_SHOW enter")
	if (MerchantFrame:IsVisible() and not InRepairMode()) then
		-- update sell prizes, if needed
		local changed = false
		for bag = 0, 4 do
			for slot = 1, GetContainerNumSlots(bag) do
				itemLink = GetContainerItemLink(bag, slot);
				if (itemLink) then
					local itemId = _ExtractItemId(itemLink);
					local sellPrice = nil
					self.tooltip.sellPrice = nil;
					self.tooltip:ClearLines()
					self.tooltip:SetBagItem(bag, slot);
					_ScanTooltip(self.tooltip)
					if (self.tooltip.sellPrice) then
						local _, quantity = GetContainerItemInfo(bag, slot);
						sellPrice = self.tooltip.sellPrice / quantity
					end
					if (self.tooltip.isUnsellable) then
						sellPrice = 0
					end
					if (self.tooltip.isChargeable) then
						log:Debug("chargeable item: "..itemId)
						sellPrice = nil
					end
					local dbSellPrice = iprice:GetPrice(itemId);
					if (sellPrice and (not dbSellPrice or (dbSellPrice ~= sellPrice))) then
						local name = GetItemInfo(itemLink)
						log:Debug("SellPrizes:MERCHANT_SHOW remember itemId: "..itemId.." sellPrice: "..sellPrice.." dbSellPrice: "..(dbSellPrice or "NA").." name: "..(name or "NA"))
						self.db.global.prizes[itemId] = sellPrice
						changed = true
					end
				end
			end
		end
		if (changed) then
			_BuildChecksum(self)
		end
	end		
	log:Debug("SellPrizes:MERCHANT_SHOW exit")
end

--[[
	Returns the sell prize for the given item, or nil.
--]]
function vendor.SellPrizes:GetSellPrice(itemLink)
	local itemId = _ExtractItemId(itemLink);
	-- check own db
	local sellPrize = self.db.global.prizes[itemId]
	if (not sellPrize) then
		-- look for sell prize in ItemPrice
		sellPrize = iprice:GetPrice(itemId)
		log:Debug("GetSellPrice found in db: %d", sellPrize or 0)
	end
	return sellPrize;
end

--[[
	Callback for Tooltip integration
--]]
function vendor.SellPrizes:AppendToGameTooltip(tooltip, itemLink, itemName, count)
	if (not MerchantFrame:IsVisible() and self.db.profile.showPrizes) then
		local sellPrize = self:GetSellPrice(itemLink);
		if (sellPrize) then
			local msg1 = " "
			local msg2
			if (sellPrize == 0) then
				msg2 = ITEM_UNSELLABLE 
			elseif (self.db.profile.showLabel) then
				if (count > 1) then
					msg1 = L["Sales price (%d)"]:format(count);
					msg2 = vendor.Format.FormatMoney(sellPrize, true).." ("..vendor.Format.FormatMoney(count * sellPrize, true)..")";
				else
					msg1 = L["Sales price"];
					msg2 = vendor.Format.FormatMoney(sellPrize, true);
				end
			else
				msg2 = vendor.Format.FormatMoney(sellPrize, true);
			end
			tooltip:AddDoubleLine(msg1, msg2);
		end
   	end
end
